package com.zkh.myframe.request;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;

import com.zkh.myutils.bean.BeanArrayConverter;
import com.zkh.myutils.code.CoderUtils;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.json.JSONArray;
import com.zkh.myutils.json.JSONObject;
import com.zkh.myutils.net.http.HttpUtils;
import com.zkh.myutils.net.http.RequestParam;
import com.zkh.myutils.proxy.MethodHandler;
import com.zkh.myutils.proxy.ProxyMethod;
import com.zkh.myutils.system.ConfigLoader;
import com.zkh.myutils.utils.Regexp;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;

/**
 * 请求接口代理处理器
 * @author zkh
 */
public class RequestProxy implements MethodHandler {
	//变量正则
	private final static String VAR_REG = "\\$\\{[^}]+\\}";
	//方法注解（方法对应的注解）
	private Map<String, RequestParam> methodReqParamMap = new HashMap<>();
	//原始Method入参的参数对象
	private Map<String, Parameter[]> methodParamMap = new HashMap<>();
	//方法对应请求地址
	private Map<String, String> methodUrlMap = new HashMap<>();
	
	/**
	 * 构造方法
	 * @param intfClass 发送请求的接口Class
	 */
	public RequestProxy(Class<?> intfClass) {
		//接口注解
		Request request = intfClass.getAnnotation(Request.class);
		//获取所有接口方法
		Method[] methods = intfClass.getMethods();
		//遍历
		for(Method method: methods) {
			//获取请求属性注解
			RequestAttr attr = method.getAnnotation(RequestAttr.class);
			//存在
			if(attr!=null) {
				//获取方法的唯一标识
				String unique = getMethodUnique(method.getName(), method.getParameterTypes());
				//请求地址前缀
				String prefix = request.prefix();
				//获取变量
				List<String> vars = Regexp.getMatchesAll(VAR_REG, prefix);
				//如果请求地址有变量，则依次替换
				for(String var: vars) {
					//解析变量值
					Object obj = Utils.parseValue(var.substring(2, var.length()-1), ConfigLoader.getAll());
					//替换
					prefix = prefix.replace(var, obj==null ? "null" : obj.toString());
				}
				//保存地址
				methodUrlMap.put(unique, prefix + attr.uri());
				//请求参数对象
				RequestParam param = new RequestParam();
				//设置超时时间
				param.setConnectTimeout(request.connectTimeout());
				param.setReadTimeout(request.readTimeout());
				param.setMethod(attr.method());
				//遍历属性
				for(String prop: attr.props()) {
					//发送JSON
					if("application/json".equalsIgnoreCase(prop)) {
						param.setContentType("application/json");
					}else {
						//分隔符
						int sep = prop.indexOf('=');
						//无分隔符
						if(sep==-1) {
							throw new IllegalArgumentException(intfClass.getName()+"."+method.getName()+"方法的注解@RequestAttr属性props配置格式错误："+prop);
						}
						//按分隔符分割键值
						String key = prop.substring(0, sep), value = prop.substring(sep+1);
						//按key设置值
						switch (key.toLowerCase()) {
							case "user-agent":
								param.setUserAgent(value);break;
							case "accept":
								param.setAccept(value);break;
							case "accept-encoding":
								param.setAcceptEncoding(value);break;
							case "accept-language":
								param.setAcceptLang(value);break;
							case "connection":
								param.setConnection(value);break;
							case "content-length":
								param.setContentLength(Long.parseLong(value));break;
							case "content-type":
								param.setContentType(value);break;
							case "referer":
								param.setReferer(value);break;
							case "pragma":
								param.setPragma(value);break;
							case "cache-control":
								param.setCacheControl(value);break;
							case "authorization":
								param.setAuthorization(value);break;
							case "host":
								param.setHost(value);break;
							case "cookie":
								param.setCookie(value);break;
							default:
								throw new IllegalArgumentException(intfClass.getName()+"."+method.getName()+"方法的注解@RequestAttr属性props配置未知的Key值："+key);
						}
					}
				}
				//判断方法返回值是否InputStream为流，如果是，响应直接以流的方式保存，不解析为字符串
				if(InputStream.class.isAssignableFrom(method.getReturnType())) {
					if(!InputStream.class.getName().equals(method.getReturnType().getName())) {
						throw new RuntimeException("返回流时只能为InputStream类型");
					}
					//保存结果值为流
					param.setSaveAsStream(true);
				}
				//保存方法的请求入参对象
				methodReqParamMap.put(unique, param);
				//获取方法入参对象
				methodParamMap.put(unique, method.getParameters());
			}else {
				Logger.info("接口{}的{}方法没有RequestAttr注解", intfClass.getName(), method.getName());
			}
		}
	}

	/**
	 * 获取方法的唯一标识
	 * @param methodName 方法名
	 * @param paramTypes 方法入参类型
	 * @return 方法的唯一标识
	 */
	private String getMethodUnique(String methodName, Class<?>[] paramTypes) {
		// 标识容器
		StringBuilder sb = new StringBuilder();
		//遍历取值
		for(Class<?> ptype: paramTypes) {
			sb.append(ptype.getTypeName());
		}
		return methodName + "@" + sb.toString();
	}

	/**
	 * 调用方法
	 */
	@Override
	public Object invoke(Object proxy, ProxyMethod proxyMethod) throws Throwable {
		//方法对象
		Method method = proxyMethod.getMethod();
		//方法唯一标识
		String unique = getMethodUnique(proxyMethod.getSuperName(), method.getParameterTypes());
		//获取发送请求的参数对象
		RequestParam param = methodReqParamMap.get(unique);
		//为空
		if(param==null) {
			throw new RuntimeException(proxyMethod.getSuperClassName()+"."+proxyMethod.getSuperName()+"()方法未使用@RequestAttr注解，不能发送请求调用");
		}
		//复制一个RequestParam对象，避免并发操作数据异常
		param = param.clone();
		//参数容器
		Map<String, Object> params = new TreeMap<>();
		//指定参数体
		Object requestBody = null;
		//参数对象（不能使用method.getParameters()获取，因为method是代理方法，不是原始方法）
		Parameter[] ps = methodParamMap.get(unique);
		//获取方法入参
		for(int i=0,len=ps.length; i<len; i++) {
			//当前参数对象
			Parameter p = ps[i];
			//参数值
			Object pv = proxyMethod.getArgs()[i];
			//参数体
			if(p.isAnnotationPresent(RequestBody.class)) {
				requestBody = pv;
			}
			//保存参数
			params.put(p.getName(), pv);
		}
		//请求地址
		String url = methodUrlMap.get(unique);
		//获取变量
		List<String> vars = Regexp.getMatchesAll(VAR_REG, url);
		//如果请求地址有变量，则依次替换
		for(String var: vars) {
			//解析变量值
			Object obj = Utils.parseValue(var.substring(2, var.length()-1), params);
			//替换
			url = url.replace(var, obj==null ? "null" : obj.toString());
		}
		//保存地址
		param.setUrl(url);
		//打印日志
		Logger.debug("请求地址：{}", url);
		//指定RequestBody且为字符串
		if(requestBody instanceof String) {
			param.setParam((String) requestBody);
		}//处理请求参数（POST请求传递JSON）
		else if("application/json".equalsIgnoreCase(param.getContentType()) && "post".equalsIgnoreCase(param.getMethod())) {
			//如果指定RequestBody，则使用该参数作为请求参数，否则使用接口方法作为入参
			param.setParam(new JSONObject(Optional.ofNullable(requestBody).orElse(params)).toString());
		}//否则拼接k1=v1&k2=v2这种格式
		else {
			//发送参数容器
			List<String> sendParams = new ArrayList<>();
			//存在RequestBody
			if(requestBody!=null) {
				//转JSON解析对象
				JSONObject jsonObject = new JSONObject(requestBody);
				//遍历赋值参数
				jsonObject.eachKey(key->sendParams.add(key+"="+CoderUtils.encodeURL(jsonObject.getStringValue(key))));
			}//不存在RequestBody
			else {
				params.forEach((key, value) -> {
					//值不为null
					if (value != null) {
						sendParams.add(key + "=" + CoderUtils.encodeURL(value.toString()));
					}
				});
			}
			//保存
			param.setParam(sendParams.isEmpty() ? "" : String.join("&", sendParams));
		}
		//打印日志
		Logger.debug("请求参数：{}", param.getParam());
		//发送请求
		String result = HttpUtils.sendRequest(param);
		//结果值为流
		if(param.isSaveAsStream()) {
			return param.getInputStream();
		}
		//为空
		if(result==null || result.isEmpty()) {
			return result;
		}
		//响应类型
		Class<?> rtnType = method.getReturnType();
		//转换类型并返回
		return StringUtils.castType(result, rtnType.getName(), typeName->{
			//响应泛型类型
			String genericType = method.getGenericReturnType().getTypeName();
			try {
				//数组类型
				if(rtnType.isArray()) {
					//JSON格式化并转换为集合
					List<?> list = new JSONArray(result).parseList();
					//转换类型
					return BeanArrayConverter.toArray(list, list.getClass(), rtnType);
				}//集合
				else if(List.class.isAssignableFrom(rtnType)) {
					//JSON格式化并转换为集合
					List<?> list = new JSONArray(result).parseList();
					//转换类型
					return BeanArrayConverter.toList(list, list.getClass(), rtnType, genericType);
				}//Map
				else if(Map.class.isAssignableFrom(rtnType)) {
					//JSON格式化并转换为集合
					return new JSONObject(result).parseEntity(rtnType, genericType);
				}//实体
				else {
					return new JSONObject(result).parseEntity(rtnType);
				}
			}catch (Exception e) {
				throw new RuntimeException("结果值转换失败：\r\n结果值：" + result + "\r\n转换类型：" + typeName + "\r\n", e);
			}
		});
	}
}
